package main

import (
	"database/sql"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/garyburd/redigo/redis"
	_ "github.com/go-sql-driver/mysql"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"sync"
	"time"
)

var TimeLayout string = "2006-01-02 15:04:05"
var Db *sql.DB

type Post struct {
	Id       int64  `xml:"id,attr" json:"id"`          //xml attr
	UserId   int    `xml:"user_id" json:"user_id"`     //
	UserName string `xml:"user_name" json:"user_name"` //
	Message  string `xml:"message" json:"message"`     //
	FilePath string `xml:"file_path" json:"file_path"` //
	Status   int    `xml:"status,attr" json:"status"`  //xml attr
	DateTime string `xml:"date_time" json:"date_time"` //
}

func init() {
	var err error
	// >go get github.com/go-sql-driver/mysql
	Db, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8")
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	Db.SetMaxOpenConns(100)
}

func indexHandle(w http.ResponseWriter, r *http.Request) {
	var str = `<html>
	<head></head>
	<body>
		<form enctype="multipart/form-data" action="/postFile?a=1&b=2,3,4" method="POST">
			UserId:<input type="text" name="UserId" value="100" /><br />
			UserName:<input type="text"" name="UserName" value ="LiXiaoCheng" /><br />
			Message:<input type="text"" name="Message" value ="" /><br />
			File:<input name="UserFile" type="file" /><br />
			<input type="submit" value="Send File" />
		</form>
	</body>
</html>
`
	w.Write([]byte(str))
}

func testHandle(w http.ResponseWriter, r *http.Request) {

	w.WriteHeader(201) //HTTP状态码

	//获取Cookie
	c1, err := r.Cookie("c1")
	if err != nil {
		fmt.Fprintln(w, "c1 cannot get")
	} else {
		fmt.Fprintln(w, "c1 = '"+c1.Value+"'")
	}

	c2, _ := r.Cookie("c2")
	fmt.Fprintln(w, c2)

	fmt.Fprintf(w, "<pre>Hello %s.</pre>\r\n", "lxc")

	//打印Header
	fmt.Fprintln(w, r.Header)
	fmt.Fprintln(w, r.URL.String())
	fmt.Fprintln(w, "")

	//打印Form
	r.ParseForm()
	fmt.Fprintln(w, r.Form)
	fmt.Fprintln(w, "")

}

func postHandle(w http.ResponseWriter, r *http.Request) {

	var p Post
	p.DateTime = time.Now().Format(time.RFC3339)
	err0 := r.ParseMultipartForm(1024 * 1024 * 10)
	if err0 != nil {
		fmt.Println(err0)
		return
	}
	p.UserName = r.MultipartForm.Value["UserName"][0]
	p.Message = r.MultipartForm.Value["Message"][0]
	id, err := strconv.Atoi(r.MultipartForm.Value["UserId"][0])
	if err == nil {
		p.UserId = id
	}

	isSendFile := false
	file, handle, err := r.FormFile("UserFile")
	if file != nil && handle != nil && err == nil {
		isSendFile = true
	}
	if file != nil {
		file.Close()
	}
	if handle != nil && handle.Size > 5*1024*1024 {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte(string("上传的文件大小超过5M")))
		return
	}

	if isSendFile {
		path := "file/"

		//判断目录的存在性(匿名函数)
		isDirExists := func(dirPath string) bool {
			fileInfo, err2 := os.Stat(dirPath)
			if err2 != nil {
				return os.IsExist(err2)
			} else {
				return fileInfo.IsDir()
			}
		}(path)

		//系统的分隔符
		DirectorySeparator := ""
		if os.IsPathSeparator('\\') {
			DirectorySeparator = "\\"
		} else {
			DirectorySeparator = "/"
		}

		dir, _ := os.Getwd()                         //当前的目录
		p.FilePath = dir + DirectorySeparator + path //
		//创建目录
		if isDirExists == false {
			fmt.Println("create newDir:" + p.FilePath)
			err3 := os.Mkdir(p.FilePath, os.ModePerm) //在当前目录下生成path目录
			if err3 != nil {
				fmt.Println(err3)
			}
		}

		p.FilePath += handle.Filename
		f, _ := os.Create(p.FilePath)
		defer f.Close()
		written, err3 := io.Copy(f, file)
		if err3 != nil {
			fmt.Println(written)
			fmt.Println(err3.Error())
		}
		fmt.Printf("上传文件的大小为: %.3fM\n", float64(handle.Size)/float64(1024*1024))
	}

	//ch := make(chan int64, 10) //创建一个通道
	var wg sync.WaitGroup //声明一个等待组

	wg.Add(3) //为计数器设置值

	//goroutine
	go func(p Post, wg *sync.WaitGroup) {
		//保存到数据库中
		now := time.Now().Format("2006-01-02 15:04:05")
		res, err := Db.Exec("INSERT INTO userPost(userId,userName,message,filePath,status,cTime,uTime)VALUES (?,?,?,?,?,?,?)", p.UserId, p.UserName, p.Message, p.FilePath, 0, now, now)
		if err != nil {
			fmt.Println([]byte("Insert into mysql error:\r\n" + err.Error()))
		} else {
			p.Id, _ = res.LastInsertId()
			fmt.Printf("LastInsertId:%d\n", p.Id)
		}

		fmt.Println("save to mysql success.")
		wg.Done() //对计数器减一操作
	}(p, &wg)

	//goroutine
	go func(posts Post, wg *sync.WaitGroup) {
		//保存json数据到log文件
		jsonStr, _ := json.Marshal(posts)
		//追加写入文件
		fd, _ := os.OpenFile("output.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		fd.Write([]byte(string(jsonStr) + "\n"))
		defer fd.Close()

		fmt.Println("save to file success.")
		wg.Done() //对计数器减一操作
	}(p, &wg)

	//goroutine
	go func(wg *sync.WaitGroup) {

		//暂停3S
		//time.Sleep(2 * time.Second)

		fmt.Println("do other things.")
		wg.Done() //对计数器减一操作
	}(&wg)

	wg.Wait() //塞到计数器的值为0

	w.Header().Set("Location", "/info?id="+strconv.FormatInt(p.Id, 10)+"&format=xml")
	w.WriteHeader(302)
}

//展示数据
func infoHandle(w http.ResponseWriter, r *http.Request) {
	type Return struct {
		Error   int         `json:"error"`
		Message string      `json:"message"`
		Data    interface{} `json:"data"`
	}
	var returnStr Return
	var postId int64

	format := r.URL.Query().Get("format")
	if format != "json" {
		format = "xml"
		w.Header().Set("Content-Type", "text/xml; charset=utf-8")
	} else {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
	}

	var strOut []byte

	//字符串转int64
	postId, err := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64)
	if err != nil {
		returnStr.Error = 1
		returnStr.Message = "参数错误"
		if format == "json" {
			strOut, _ = json.MarshalIndent(returnStr, "", "\t")
		} else if format == "xml" {
			strOut, _ = xml.MarshalIndent(returnStr, "", "\t")
		}
		w.Write(strOut)
		return
	}

	//查询数据
	sql := "select id,userId,userName,message,filePath,`status`,cTime from userPost where id=?"
	var p Post
	err2 := Db.QueryRow(sql, strconv.FormatInt(postId, 10)).Scan(&p.Id, &p.UserId, &p.UserName, &p.Message, &p.FilePath, &p.Status, &p.DateTime)
	if err2 != nil {
		returnStr.Error = 1
		returnStr.Message = "查询出错"
		if format == "json" {
			strOut, _ = json.MarshalIndent(returnStr, "", "\t")
		} else if format == "xml" {
			strOut, _ = xml.MarshalIndent(returnStr, "", "\t")
		}
		w.Write(strOut)
		return
	}
	returnStr.Error = 0
	returnStr.Message = "success"
	returnStr.Data = p
	if format == "json" {
		strOut, _ = json.MarshalIndent(returnStr, "", "\t")
	} else if format == "xml" {
		strOut, _ = xml.MarshalIndent(returnStr, "", "\t")
	}
	w.Write(strOut)
}

//自动跳转
func redirectHandle(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Location", "/test?a1=v1&b2=v2")
	w.WriteHeader(302)
}

//Json返回
func jsonHandle(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	type Post struct {
		User    string `json:"user"`
		Id      int    `json:"id"`
		Address string `json:"address"`
	}

	p := &Post{User: "lxc", Id: 123, Address: "beijing"}
	jsonStr, _ := json.Marshal(p)
	w.Write(jsonStr)
}

//setCookie
func setCookieHandle(w http.ResponseWriter, r *http.Request) {
	c1 := http.Cookie{Name: "c1", Value: "vC1", HttpOnly: true}
	c2 := http.Cookie{Name: "c2", Value: "vC2", HttpOnly: false, Expires: time.Now().AddDate(0, 0, 7)}

	//两种方式都可以
	w.Header().Set("Set-Cookie", c1.String())
	http.SetCookie(w, &c2)

	w.Header().Set("Location", "/test?a1=v1&b2=v2")
	w.WriteHeader(302)
}

/**
Redis连接池
*/
// 连接池大小
var MAX_POOL_SIZE = 20
var redisPoll chan redis.Conn

func putRedis(conn redis.Conn) {
	// 基于函数和接口间互不信任原则，这里再判断一次，养成这个好习惯哦
	if redisPoll == nil {
		redisPoll = make(chan redis.Conn, MAX_POOL_SIZE)
	}
	if len(redisPoll) >= MAX_POOL_SIZE {
		conn.Close()
		return
	}
	redisPoll <- conn
}
func InitRedis(network, address string) redis.Conn {
	// 缓冲机制，相当于消息队列
	if len(redisPoll) == 0 {
		// 如果长度为0，就定义一个redis.Conn类型长度为MAX_POOL_SIZE的channel
		redisPoll = make(chan redis.Conn, MAX_POOL_SIZE)
		go func() {
			for i := 0; i < MAX_POOL_SIZE/2; i++ {
				c, err := redis.Dial(network, address)
				if err != nil {
					panic(err)
				}
				putRedis(c)
			}
		}()
	}
	return <-redisPoll
}
func redisServer(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	c := InitRedis("tcp", "192.168.0.103:6379")
	dbKey := "test_1"
	if ok, err := redis.Bool(c.Do("LPUSH", dbKey, time.Now().Format(TimeLayout))); ok {
	} else {
		log.Print(err)
	}
	msg := fmt.Sprintf("用时：%s", time.Now().Sub(startTime))
	io.WriteString(w, msg+"\n\n")
}

//main
func main2() {

	// 利用cpu多核来处理http请求，这个没有用go默认就是单核处理http的，这个压测过了，请一定要相信我
	runtime.GOMAXPROCS(runtime.NumCPU())
	mux := http.NewServeMux()
	mux.HandleFunc("/test", testHandle)
	mux.HandleFunc("/postFile", postHandle)
	mux.HandleFunc("/redirect", redirectHandle)
	mux.HandleFunc("/json", jsonHandle)
	mux.HandleFunc("/setCookie", setCookieHandle)
	mux.HandleFunc("/info", infoHandle)
	mux.HandleFunc("/", indexHandle)
	mux.HandleFunc("/redis", redisServer)

	server := &http.Server{
		Addr:    "0.0.0.0:8080",
		Handler: mux,
	}

	server.ListenAndServe()

}

type routerCFG struct {
	Regexp   string
	KeyNames []string
	Handler  interface{}
}

func IndexHandler(w http.ResponseWriter, r *http.Request) {
	uri := r.RequestURI
	Regexp := regexp.MustCompile(`\/read\/(\d)+\.html`)
	param := Regexp.FindStringSubmatch(uri)
	fmt.Println(param)

	w.Write([]byte("hello world"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", IndexHandler)
	server := &http.Server{
		Addr:    "0.0.0.0:8080",
		Handler: mux,
	}

	server.ListenAndServe()
}
